/*
 *
 *      Copyright (c) 2018-2025, lengleng All rights reserved.
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions are met:
 *
 * Redistributions of source code must retain the above copyright notice,
 *  this list of conditions and the following disclaimer.
 *  Redistributions in binary form must reproduce the above copyright
 *  notice, this list of conditions and the following disclaimer in the
 *  documentation and/or other materials provided with the distribution.
 *  Neither the name of the pig4cloud.com developer nor the names of its
 *  contributors may be used to endorse or promote products derived from
 *  this software without specific prior written permission.
 *  Author: lengleng (wangiegie@gmail.com)
 *
 */

package com.liycloud.auth.config;

import cn.hutool.core.util.StrUtil;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.liycloud.auth.service.PigxRedisTokenStore;
import com.liycloud.common.core.constant.SecurityConstants;
import com.liycloud.common.data.tenant.TenantContextHolder;
import com.liycloud.common.security.component.PigxCommenceAuthExceptionEntryPoint;
import com.liycloud.common.security.component.PigxWebResponseExceptionTranslator;
import lombok.AllArgsConstructor;
import lombok.SneakyThrows;

import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;

// spring-security
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.core.userdetails.UserDetailsService;

// spring-security-oauth2
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.ClientDetailsService;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.code.AuthorizationCodeServices;
import org.springframework.security.oauth2.provider.token.DefaultAuthenticationKeyGenerator;
import org.springframework.security.oauth2.provider.token.TokenEnhancer;

/**
 * @author lengleng
 * @date 2018/6/22  认证服务器配置
 */
@Configuration
@AllArgsConstructor
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {

	/*
	 * ClientDetails 客户端信息
	 */
	private final ClientDetailsService pigxClientDetailsServiceImpl;

	/*
	 * 认证管理器
	 */
	private final AuthenticationManager authenticationManagerBean;

	/*
	 * 用户信息
	 */
	private final UserDetailsService pigxUserDetailsService;

	/*
	 * Token 存储器
	 */
	private final PigxRedisTokenStore redisTokenStore;

	private final AuthorizationCodeServices authorizationCodeServices;  // 授权码服务

	private final TokenEnhancer pigxTokenEnhancer;  // Token 加强器

	private final ObjectMapper objectMapper;  // com.fasterxml.jackson.databind.ObjectMapper  对象映射


	/**
	 * 客户端信息
	 */
	@Override
	@SneakyThrows
	public void configure(ClientDetailsServiceConfigurer clients) {
		clients.withClientDetails(pigxClientDetailsServiceImpl);
	}


	/**
	 * 认证授权服务器的安全配置
	 */
	@Override
	public void configure(AuthorizationServerSecurityConfigurer oauthServer) {

		oauthServer.allowFormAuthenticationForClients()  // 允许表单Form 方式请求认证
				.authenticationEntryPoint(new PigxCommenceAuthExceptionEntryPoint(objectMapper))  //
				.checkTokenAccess("isAuthenticated()");  // 允许检查 Token
	}


	/**
	 *
	 * 认证授权服务 EndpointsConfigurer
	 * @param endpoints
	 */
	@Override
	public void configure(AuthorizationServerEndpointsConfigurer endpoints) {

		// 设置tokenStore 前缀相关
		String prefix = String.format(":%s%s", SecurityConstants.PIGX_PREFIX, SecurityConstants.OAUTH_PREFIX);

		redisTokenStore.setPrefix(prefix);

		// 设置区分key 条件
		redisTokenStore.setAuthenticationKeyGenerator(new DefaultAuthenticationKeyGenerator() {

			// extract  v. 提取，提炼；索取
			@Override
			public String extractKey(OAuth2Authentication authentication) {
				return super.extractKey(authentication) + StrUtil.COLON + TenantContextHolder.getTenantId();
			}
		});

		endpoints.allowedTokenEndpointRequestMethods(HttpMethod.GET, HttpMethod.POST)  // 允许获取Token的请求方法类型

				.tokenStore(redisTokenStore)  // Token 存储器

				.tokenEnhancer(pigxTokenEnhancer)  // Token 加强器

				.userDetailsService(pigxUserDetailsService)  // 用户信息服务

				.authorizationCodeServices(authorizationCodeServices)  // 授权码服务

				.authenticationManager(authenticationManagerBean)  // 认证管理器

				.reuseRefreshTokens(false)  // 不允许重复使用 Token

				.pathMapping("/oauth/confirm_access", "/token/confirm_access")  // 更改框架默认的请求行为路径

				.exceptionTranslator(new PigxWebResponseExceptionTranslator());   // 异常翻译器
	}

}
